package com.goldfish666.houghrecognition.util;

import lombok.extern.slf4j.Slf4j;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.List;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
public class RecognitionUtil {

    private static Map<String, String> map = new HashMap<>();

    static class RecognitionResult {
        private final int indexX;

        private final String target;

        private final int totalCount;

        private final int availableCount;

        private final int onCount;

        private RecognitionResult(String target, int indexX, int totalCount, int availableCount, int onCount) {
            this.target = target;
            this.totalCount = totalCount;
            this.availableCount = availableCount;
            this.onCount = onCount;
            this.indexX = indexX;
        }

        public int getIndexX() {
            return indexX;
        }

        public String getTarget() {
            return target;
        }

        public int getTotalCount() {
            return totalCount;
        }

        public int getAvailableCount() {
            return availableCount;
        }

        public int getOnCount() {
            return onCount;
        }

        public double getOnRate() {
            return onCount * 1.0 / availableCount;
        }
    }

    public static class Result {
        private final String target;
        private final ShapeUtil.PointInCartesian point;
        private final int count;

        public Result(String target, ShapeUtil.PointInCartesian point, int count) {
            this.target = target;
            this.point = point;
            this.count = count;
        }

        public String getTarget() {
            return target;
        }

        public ShapeUtil.PointInCartesian getPoint() {
            return point;
        }

        public int getCount() {
            return count;
        }
    }

    /**
     * 根据只包含目标字符的图片生成骨架图
     *
     * @param s        字符名称
     * @param readPath 原图片所在位置
     * @param savePath 目标路径
     * @throws IOException
     */
    public static void generateShapeBasePicture(String s, String readPath, String savePath) throws IOException {
        HoughImageUtil.draw(HoughImageUtil.getContentImage(HoughImageUtil.skeletonize(HoughImageUtil.getBinaryImage(HoughImageUtil.getGrayImage(HoughImageUtil.read(readPath))))), savePath, s);
    }

    public static void drawResult(List<Result> list, String path, String name) throws IOException {
        int w = list.stream().map(r -> r.getPoint().getX()).max(Comparator.comparingInt(n -> n)).get();
        list = list.stream().sorted(Comparator.comparingInt(r -> r.getPoint().getX())).collect(Collectors.toList());
        int h = list.stream().map(Result::getCount).max(Comparator.comparingInt(i -> i)).get() + 1;
        // 初始化
        BufferedImage bufferedImage = new BufferedImage(w, h + 10, 2);
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h + 10; j++) {
                bufferedImage.setRGB(i, j, new Color(255, 255, 255).getRGB());
            }
        }
        // 填充次数
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h + 10; j++) {
                int finalI = i;
                Optional<Result> optionalResult = list.stream().filter(r -> r.getPoint().getX() == finalI).findFirst();
                if (optionalResult.isPresent()) {
                    int y = optionalResult.get().getCount();
                    if (j >= h - y + 10) {
                        bufferedImage.setRGB(i, j, new Color(0, 0, 0).getRGB());
                    }
                }
            }
        }

        // 填充目标
        for (int i = 0; i < w; i++) {
            for (int j = 0; j < h + 10; j++) {
                int finalI = i;
                Optional<Result> optionalResult = list.stream().filter(r -> r.getPoint().getX() == finalI).findFirst();
                if (optionalResult.isPresent()) {
                    int target = Integer.parseInt(optionalResult.get().getTarget());
                    if (j == target) {
                        bufferedImage.setRGB(i, j, new Color(0, 0, 0).getRGB());
                    }
                }
            }
        }
        HoughImageUtil.draw(bufferedImage, path, name);
    }

    public static List<Result> recognise(String captchaPath, Set<String> dictionary) throws IOException {
        List<Result> recognitionResultList = new ArrayList<>();
        BufferedImage toRecognisePicture = HoughImageUtil.skeletonize(HoughImageUtil.getBinaryImage(HoughImageUtil.getGrayImage(HoughImageUtil.read(captchaPath))));
        HoughImageUtil.ImageInfo imageInfo = HoughImageUtil.getImageInfo(toRecognisePicture);
        for (String s : dictionary) {
            ShapeUtil.Shape shape = ShapeUtil.getShape(s, HoughImageUtil.read(Constant.getSkeletonDictionary() + Constant.getTargetNameMap().get(s)));
            List<Result> currentRecognitionResultList = recognise(shape, toRecognisePicture, imageInfo);
            recognitionResultList.addAll(currentRecognitionResultList);
        }
        Map<ShapeUtil.PointInCartesian, List<Result>> pointMap = recognitionResultList.stream().collect(Collectors.groupingBy(Result::getPoint));
        Map<ShapeUtil.PointInCartesian, Integer> multi = new LinkedHashMap<>();
        Map<ShapeUtil.PointInCartesian, Integer> single = new LinkedHashMap<>();

        for (Map.Entry<ShapeUtil.PointInCartesian, List<Result>> entry : pointMap.entrySet()) {
            List<Result> list = entry.getValue();
            if (list.size() != 1) {
                int max = 0;
                int sum = 0;
                for (Result r : list) {
                    sum += r.getCount();
                    if (r.getCount() > max) {
                        max = r.getCount();
                    }
                }
                multi.put(entry.getKey(), (int) (100.0 * max / sum));
            } else {
                single.put(entry.getKey(), list.get(0).getCount());
            }
        }
        List<ShapeUtil.PointInCartesian> toDeleteList = new ArrayList<>(single.keySet());
        for (Map.Entry<ShapeUtil.PointInCartesian, Integer> entry : multi.entrySet()) {
            if (entry.getValue() < 55) {
                toDeleteList.add(entry.getKey());
            }
        }
        Map<Integer, List<Result>> map = recognitionResultList.stream().filter(o -> !toDeleteList.contains(o.getPoint())).collect(Collectors.groupingBy(r -> r.getPoint().getX()));
        List<Result> results = new ArrayList<>();
        for (int i : map.keySet()) {
            results.add(map.get(i).stream().max(Comparator.comparingInt(Result::getCount)).get());
        }
        return results;
    }


    // 识别path中的图片是否存在n
    public static List<Result> recognise(String n, String path) throws IOException {
        ShapeUtil.Shape shape = ShapeUtil.getShape(n, HoughImageUtil.read(map.get(n)));
        BufferedImage toRecognisePicture = HoughImageUtil.skeletonize(HoughImageUtil.getBinaryImage(HoughImageUtil.getGrayImage(HoughImageUtil.read(path))));
        HoughImageUtil.ImageInfo imageInfo = HoughImageUtil.getImageInfo(toRecognisePicture);
        return recognise(shape, toRecognisePicture, imageInfo);
    }

    public static List<Result> recognise(ShapeUtil.Shape shape, BufferedImage bufferedImage, HoughImageUtil.ImageInfo imageInfo) {
        List<ShapeUtil.PointInCartesian> referenceList = new ArrayList<>();
        for (int i = 0; i < imageInfo.getWidth(); i++) {
            for (int j = 0; j < imageInfo.getHeight(); j++) {
                if (HoughImageUtil.getGray(bufferedImage.getRGB(i, j)) < imageInfo.getT()) {
                    referenceList.addAll(shape.getReferencePointList(new ShapeUtil.PointInCartesian(i, j)).stream()
                            .filter(p -> p.getX() >= 0 && p.getX() < imageInfo.getWidth()
                                    && p.getY() >= 0 && p.getY() < imageInfo.getHeight()).collect(Collectors.toList()));
                }
            }
        }
        Map<ShapeUtil.PointInCartesian, Integer> map = new LinkedHashMap<>();
        for (ShapeUtil.PointInCartesian p : referenceList) {
            if (map.containsKey(p)) {
                map.put(p, map.get(p) + 1);
            } else {
                map.put(p, 1);
            }
        }
        List<Result> results = new ArrayList<>();

        for (Map.Entry<ShapeUtil.PointInCartesian, Integer> entry : map.entrySet()) {
            results.add(new Result(shape.getName(), entry.getKey(), entry.getValue()));
        }
        return results;
    }


}
